home *** CD-ROM | disk | FTP | other *** search
/ PC Advisor 2010 April / PCA177.iso / ESSENTIALS / Firefox Setup.exe / nonlocalized / chrome / browser.jar / content / browser / places / controller.js < prev    next >
Encoding:
Text File  |  2009-07-15  |  58.7 KB  |  1,628 lines

  1. /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  2. /* ***** BEGIN LICENSE BLOCK *****
  3.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  4.  *
  5.  * The contents of this file are subject to the Mozilla Public License Version
  6.  * 1.1 (the "License"); you may not use this file except in compliance with
  7.  * the License. You may obtain a copy of the License at
  8.  * http://www.mozilla.org/MPL/
  9.  *
  10.  * Software distributed under the License is distributed on an "AS IS" basis,
  11.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12.  * for the specific language governing rights and limitations under the
  13.  * License.
  14.  *
  15.  * The Original Code is the Places Command Controller.
  16.  *
  17.  * The Initial Developer of the Original Code is Google Inc.
  18.  * Portions created by the Initial Developer are Copyright (C) 2005
  19.  * the Initial Developer. All Rights Reserved.
  20.  *
  21.  * Contributor(s):
  22.  *   Ben Goodger <beng@google.com>
  23.  *   Myk Melez <myk@mozilla.org>
  24.  *   Asaf Romano <mano@mozilla.com>
  25.  *   Marco Bonardo <mak77@bonardo.net>
  26.  *
  27.  * Alternatively, the contents of this file may be used under the terms of
  28.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  29.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  30.  * in which case the provisions of the GPL or the LGPL are applicable instead
  31.  * of those above. If you wish to allow use of your version of this file only
  32.  * under the terms of either the GPL or the LGPL, and not to allow others to
  33.  * use your version of this file under the terms of the MPL, indicate your
  34.  * decision by deleting the provisions above and replace them with the notice
  35.  * and other provisions required by the GPL or the LGPL. If you do not delete
  36.  * the provisions above, a recipient may use your version of this file under
  37.  * the terms of any one of the MPL, the GPL or the LGPL.
  38.  *
  39.  * ***** END LICENSE BLOCK ***** */
  40.  
  41. // XXXmano: we should move most/all of these constants to PlacesUtils
  42. const ORGANIZER_ROOT_BOOKMARKS = "place:folder=BOOKMARKS_MENU&excludeItems=1&queryType=1";
  43. const ORGANIZER_SUBSCRIPTIONS_QUERY = "place:annotation=livemark%2FfeedURI";
  44.  
  45. // No change to the view, preserve current selection
  46. const RELOAD_ACTION_NOTHING = 0;
  47. // Inserting items new to the view, select the inserted rows
  48. const RELOAD_ACTION_INSERT = 1;
  49. // Removing items from the view, select the first item after the last selected
  50. const RELOAD_ACTION_REMOVE = 2;
  51. // Moving items within a view, don't treat the dropped items as additional 
  52. // rows.
  53. const RELOAD_ACTION_MOVE = 3;
  54.  
  55. // when removing a bunch of pages we split them in chunks to avoid passing
  56. // a too big array to RemovePages
  57. // 300 is the best choice with an history of about 150000 visits
  58. // smaller chunks could cause a Slow Script warning with a huge history
  59. const REMOVE_PAGES_CHUNKLEN = 300;
  60. // if we are removing less than this pages we will remove them one by one
  61. // since it will be reflected faster on the UI
  62. // 10 is a good compromise, since allows the user to delete a little amount of
  63. // urls for privacy reasons, but does not cause heavy disk access
  64. const REMOVE_PAGES_MAX_SINGLEREMOVES = 10;
  65.  
  66. /**
  67.  * Represents an insertion point within a container where we can insert
  68.  * items. 
  69.  * @param   aItemId
  70.  *          The identifier of the parent container
  71.  * @param   aIndex
  72.  *          The index within the container where we should insert
  73.  * @param   aOrientation
  74.  *          The orientation of the insertion. NOTE: the adjustments to the
  75.  *          insertion point to accommodate the orientation should be done by
  76.  *          the person who constructs the IP, not the user. The orientation
  77.  *          is provided for informational purposes only!
  78.  * @param   [optional] aIsTag
  79.  *          Indicates if parent container is a tag
  80.  * @param   [optional] aDropNearItemId
  81.  *          When defined we will calculate index based on this itemId
  82.  * @constructor
  83.  */
  84. function InsertionPoint(aItemId, aIndex, aOrientation, aIsTag,
  85.                         aDropNearItemId) {
  86.   this.itemId = aItemId;
  87.   this._index = aIndex;
  88.   this.orientation = aOrientation;
  89.   this.isTag = aIsTag;
  90.   this.dropNearItemId = aDropNearItemId;
  91. }
  92.  
  93. InsertionPoint.prototype = {
  94.   set index(val) {
  95.     return this._index = val;
  96.   },
  97.  
  98.   get index() {
  99.     if (this.dropNearItemId > 0) {
  100.       // If dropNearItemId is set up we must calculate the real index of
  101.       // the item near which we will drop.
  102.       var index = PlacesUtils.bookmarks.getItemIndex(this.dropNearItemId);
  103.       return this.orientation == Ci.nsITreeView.DROP_BEFORE ? index : index + 1;
  104.     }
  105.     return this._index;
  106.   }
  107. };
  108.  
  109. /**
  110.  * Places Controller
  111.  */
  112.  
  113. function PlacesController(aView) {
  114.   this._view = aView;
  115. }
  116.  
  117. PlacesController.prototype = {
  118.   /**
  119.    * The places view.
  120.    */
  121.   _view: null,
  122.  
  123.   isCommandEnabled: function PC_isCommandEnabled(aCommand) {
  124.     switch (aCommand) {
  125.     case "cmd_undo":
  126.       return PlacesUIUtils.ptm.numberOfUndoItems > 0;
  127.     case "cmd_redo":
  128.       return PlacesUIUtils.ptm.numberOfRedoItems > 0;
  129.     case "cmd_cut":
  130.       var nodes = this._view.getSelectionNodes();
  131.       // If selection includes history nodes there's no reason to allow cut.
  132.       for (var i = 0; i < nodes.length; i++) {
  133.         if (nodes[i].itemId == -1)
  134.           return false;
  135.       }
  136.       // Otherwise fallback to cmd_delete check.
  137.     case "cmd_delete":
  138.       return this._hasRemovableSelection(false);
  139.     case "placesCmd_deleteDataHost":
  140.       return this._hasRemovableSelection(false) &&
  141.         !PlacesUIUtils.privateBrowsing.privateBrowsingEnabled;
  142.     case "placesCmd_moveBookmarks":
  143.       return this._hasRemovableSelection(true);
  144.     case "cmd_copy":
  145.       return this._view.hasSelection;
  146.     case "cmd_paste":
  147.       return this._canInsert(true) && this._isClipboardDataPasteable();
  148.     case "cmd_selectAll":
  149.       if (this._view.selType != "single") {
  150.         var result = this._view.getResult();
  151.         if (result) {
  152.           var container = asContainer(result.root);
  153.           if (container.childCount > 0);
  154.             return true;
  155.         }
  156.       }
  157.       return false;
  158.     case "placesCmd_open":
  159.     case "placesCmd_open:window":
  160.     case "placesCmd_open:tab":
  161.       var selectedNode = this._view.selectedNode;
  162.       return selectedNode && PlacesUtils.nodeIsURI(selectedNode);
  163.     case "placesCmd_new:folder":
  164.     case "placesCmd_new:livemark":
  165.       return this._canInsert();
  166.     case "placesCmd_new:bookmark":
  167.       return this._canInsert();
  168.     case "placesCmd_new:separator":
  169.       return this._canInsert() &&
  170.              !asQuery(this._view.getResult().root).queryOptions.excludeItems &&
  171.              this._view.getResult().sortingMode ==
  172.                  Ci.nsINavHistoryQueryOptions.SORT_BY_NONE;
  173.     case "placesCmd_show:info":
  174.       var selectedNode = this._view.selectedNode;
  175.       if (selectedNode &&
  176.           PlacesUtils.getConcreteItemId(selectedNode) != -1  &&
  177.           !PlacesUtils.nodeIsLivemarkItem(selectedNode))
  178.         return true;
  179.       return false;
  180.     case "placesCmd_reloadMicrosummary":
  181.       var selectedNode = this._view.selectedNode;
  182.       return selectedNode && PlacesUtils.nodeIsBookmark(selectedNode) &&
  183.              PlacesUIUtils.microsummaries.hasMicrosummary(selectedNode.itemId);
  184.     case "placesCmd_reload":
  185.       // Livemark containers
  186.       var selectedNode = this._view.selectedNode;
  187.       return selectedNode && PlacesUtils.nodeIsLivemarkContainer(selectedNode);
  188.     case "placesCmd_sortBy:name":
  189.       var selectedNode = this._view.selectedNode;
  190.       return selectedNode &&
  191.              PlacesUtils.nodeIsFolder(selectedNode) &&
  192.              !PlacesUtils.nodeIsReadOnly(selectedNode) &&
  193.              this._view.getResult().sortingMode ==
  194.                  Ci.nsINavHistoryQueryOptions.SORT_BY_NONE;
  195.     default:
  196.       return false;
  197.     }
  198.   },
  199.  
  200.   supportsCommand: function PC_supportsCommand(aCommand) {
  201.     //LOG("supportsCommand: " + command);
  202.     // Non-Places specific commands that we also support
  203.     switch (aCommand) {
  204.     case "cmd_undo":
  205.     case "cmd_redo":
  206.     case "cmd_cut":
  207.     case "cmd_copy":
  208.     case "cmd_paste":
  209.     case "cmd_delete":
  210.     case "cmd_selectAll":
  211.       return true;
  212.     }
  213.  
  214.     // All other Places Commands are prefixed with "placesCmd_" ... this 
  215.     // filters out other commands that we do _not_ support (see 329587).
  216.     const CMD_PREFIX = "placesCmd_";
  217.     return (aCommand.substr(0, CMD_PREFIX.length) == CMD_PREFIX);
  218.   },
  219.  
  220.   doCommand: function PC_doCommand(aCommand) {
  221.     switch (aCommand) {
  222.     case "cmd_undo":
  223.       PlacesUIUtils.ptm.undoTransaction();
  224.       break;
  225.     case "cmd_redo":
  226.       PlacesUIUtils.ptm.redoTransaction();
  227.       break;
  228.     case "cmd_cut":
  229.       this.cut();
  230.       break;
  231.     case "cmd_copy":
  232.       this.copy();
  233.       break;
  234.     case "cmd_paste":
  235.       this.paste();
  236.       break;
  237.     case "cmd_delete":
  238.       this.remove("Remove Selection");
  239.       break;
  240.     case "placesCmd_deleteDataHost":
  241.       var host;
  242.       if (PlacesUtils.nodeIsHost(this._view.selectedNode)) {
  243.         var queries = this._view.selectedNode.getQueries({});
  244.         host = queries[0].domain;
  245.       }
  246.       else
  247.         host = PlacesUtils._uri(this._view.selectedNode.uri).host;
  248.       PlacesUIUtils.privateBrowsing.removeDataFromDomain(host);
  249.       break;
  250.     case "cmd_selectAll":
  251.       this.selectAll();
  252.       break;
  253.     case "placesCmd_open":
  254.       PlacesUIUtils.openNodeIn(this._view.selectedNode, "current");
  255.       break;
  256.     case "placesCmd_open:window":
  257.       PlacesUIUtils.openNodeIn(this._view.selectedNode, "window");
  258.       break;
  259.     case "placesCmd_open:tab":
  260.       PlacesUIUtils.openNodeIn(this._view.selectedNode, "tab");
  261.       break;
  262.     case "placesCmd_new:folder":
  263.       this.newItem("folder");
  264.       break;
  265.     case "placesCmd_new:bookmark":
  266.       this.newItem("bookmark");
  267.       break;
  268.     case "placesCmd_new:livemark":
  269.       this.newItem("livemark");
  270.       break;
  271.     case "placesCmd_new:separator":
  272.       this.newSeparator();
  273.       break;
  274.     case "placesCmd_show:info":
  275.       this.showBookmarkPropertiesForSelection();
  276.       break;
  277.     case "placesCmd_moveBookmarks":
  278.       this.moveSelectedBookmarks();
  279.       break;
  280.     case "placesCmd_reload":
  281.       this.reloadSelectedLivemark();
  282.       break;
  283.     case "placesCmd_reloadMicrosummary":
  284.       this.reloadSelectedMicrosummary();
  285.       break;
  286.     case "placesCmd_sortBy:name":
  287.       this.sortFolderByName();
  288.       break;
  289.     }
  290.   },
  291.  
  292.   onEvent: function PC_onEvent(eventName) { },
  293.  
  294.   
  295.   /**
  296.    * Determine whether or not the selection can be removed, either by the 
  297.    * delete or cut operations based on whether or not any of its contents
  298.    * are non-removable. We don't need to worry about recursion here since it
  299.    * is a policy decision that a removable item not be placed inside a non-
  300.    * removable item.
  301.    * @param aIsMoveCommand
  302.    *        True if the command for which this method is called only moves the
  303.    *        selected items to another container, false otherwise.
  304.    * @returns true if all nodes in the selection can be removed,
  305.    *          false otherwise.
  306.    */
  307.   _hasRemovableSelection: function PC__hasRemovableSelection(aIsMoveCommand) {
  308.     var ranges = this._view.getRemovableSelectionRanges();
  309.     if (!ranges.length)
  310.       return false;
  311.  
  312.     var root = this._view.getResultNode();
  313.  
  314.     for (var j = 0; j < ranges.length; j++) {
  315.       var nodes = ranges[j];
  316.       for (var i = 0; i < nodes.length; ++i) {
  317.         // Disallow removing the view's root node
  318.         if (nodes[i] == root)
  319.           return false;
  320.  
  321.         if (PlacesUtils.nodeIsFolder(nodes[i]) &&
  322.             !PlacesControllerDragHelper.canMoveNode(nodes[i]))
  323.           return false;
  324.  
  325.         // We don't call nodeIsReadOnly here, because nodeIsReadOnly means that
  326.         // a node has children that cannot be edited, reordered or removed. Here,
  327.         // we don't care if a node's children can't be reordered or edited, just
  328.         // that they're removable. All history results have removable children
  329.         // (based on the principle that any URL in the history table should be
  330.         // removable), but some special bookmark folders may have non-removable
  331.         // children, e.g. live bookmark folder children. It doesn't make sense
  332.         // to delete a child of a live bookmark folder, since when the folder
  333.         // refreshes, the child will return.
  334.         var parent = nodes[i].parent || root;
  335.         if (PlacesUtils.isReadonlyFolder(parent))
  336.           return false;
  337.       }
  338.     }
  339.  
  340.     return true;
  341.   },
  342.  
  343.   /**
  344.    * Determines whether or not nodes can be inserted relative to the selection.
  345.    */
  346.   _canInsert: function PC__canInsert(isPaste) {
  347.     var ip = this._view.insertionPoint;
  348.     return ip != null && (isPaste || ip.isTag != true);
  349.   },
  350.  
  351.   /**
  352.    * Determines whether or not the root node for the view is selected
  353.    */
  354.   rootNodeIsSelected: function PC_rootNodeIsSelected() {
  355.     var nodes = this._view.getSelectionNodes();
  356.     var root = this._view.getResultNode();
  357.     for (var i = 0; i < nodes.length; ++i) {
  358.       if (nodes[i] == root)
  359.         return true;      
  360.     }
  361.  
  362.     return false;
  363.   },
  364.  
  365.   /**
  366.    * Looks at the data on the clipboard to see if it is paste-able. 
  367.    * Paste-able data is:
  368.    *   - in a format that the view can receive
  369.    * @returns true if: - clipboard data is of a TYPE_X_MOZ_PLACE_* flavor,
  370.                        - clipboard data is of type TEXT_UNICODE and
  371.                          is a valid URI.
  372.    */
  373.   _isClipboardDataPasteable: function PC__isClipboardDataPasteable() {
  374.     // if the clipboard contains TYPE_X_MOZ_PLACE_* data, it is definitely
  375.     // pasteable, with no need to unwrap all the nodes.
  376.  
  377.     var flavors = PlacesControllerDragHelper.placesFlavors;
  378.     var clipboard = PlacesUIUtils.clipboard;
  379.     var hasPlacesData =
  380.       clipboard.hasDataMatchingFlavors(flavors, flavors.length,
  381.                                        Ci.nsIClipboard.kGlobalClipboard);
  382.     if (hasPlacesData)
  383.       return this._view.insertionPoint != null;
  384.  
  385.     // if the clipboard doesn't have TYPE_X_MOZ_PLACE_* data, we also allow
  386.     // pasting of valid "text/unicode" and "text/x-moz-url" data
  387.     var xferable = Cc["@mozilla.org/widget/transferable;1"].
  388.                    createInstance(Ci.nsITransferable);
  389.  
  390.     xferable.addDataFlavor(PlacesUtils.TYPE_X_MOZ_URL);
  391.     xferable.addDataFlavor(PlacesUtils.TYPE_UNICODE);
  392.     clipboard.getData(xferable, Ci.nsIClipboard.kGlobalClipboard);
  393.  
  394.     try {
  395.       // getAnyTransferData will throw if no data is available.
  396.       var data = { }, type = { };
  397.       xferable.getAnyTransferData(type, data, { });
  398.       data = data.value.QueryInterface(Ci.nsISupportsString).data;
  399.       if (type.value != PlacesUtils.TYPE_X_MOZ_URL &&
  400.           type.value != PlacesUtils.TYPE_UNICODE)
  401.         return false;
  402.  
  403.       // unwrapNodes() will throw if the data blob is malformed.
  404.       var unwrappedNodes = PlacesUtils.unwrapNodes(data, type.value);
  405.       return this._view.insertionPoint != null;
  406.     }
  407.     catch (e) {
  408.       // getAnyTransferData or unwrapNodes failed
  409.       return false;
  410.     }
  411.   },
  412.  
  413.   /** 
  414.    * Gathers information about the selected nodes according to the following
  415.    * rules:
  416.    *    "link"              node is a URI
  417.    *    "bookmark"          node is a bookamrk
  418.    *    "livemarkChild"     node is a child of a livemark
  419.    *    "tagChild"          node is a child of a tag
  420.    *    "folder"            node is a folder
  421.    *    "query"             node is a query
  422.    *    "dynamiccontainer"  node is a dynamic container
  423.    *    "separator"         node is a separator line
  424.    *    "host"              node is a host
  425.    *
  426.    * @returns an array of objects corresponding the selected nodes. Each
  427.    *          object has each of the properties above set if its corresponding
  428.    *          node matches the rule. In addition, the annotations names for each 
  429.    *          node are set on its corresponding object as properties.
  430.    * Notes:
  431.    *   1) This can be slow, so don't call it anywhere performance critical!
  432.    *   2) A single-object array corresponding the root node is returned if
  433.    *      there's no selection.
  434.    */
  435.   _buildSelectionMetadata: function PC__buildSelectionMetadata() {
  436.     var metadata = [];
  437.     var root = this._view.getResult().root;
  438.     var nodes = this._view.getSelectionNodes();
  439.     if (nodes.length == 0)
  440.       nodes.push(root); // See the second note above
  441.  
  442.     for (var i=0; i < nodes.length; i++) {
  443.       var nodeData = {};
  444.       var node = nodes[i];
  445.       var nodeType = node.type;
  446.       var uri = null;
  447.  
  448.       // We don't use the nodeIs* methods here to avoid going through the type
  449.       // property way too often
  450.       switch(nodeType) {
  451.         case Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY:
  452.           nodeData["query"] = true;
  453.           if (node.parent) {
  454.             switch (asQuery(node.parent).queryOptions.resultType) {
  455.               case Ci.nsINavHistoryQueryOptions.RESULTS_AS_SITE_QUERY:
  456.                 nodeData["host"] = true;
  457.                 break;
  458.               case Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_SITE_QUERY:
  459.               case Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY:
  460.                 nodeData["day"] = true;
  461.                 break;
  462.             }
  463.           }
  464.           break;
  465.         case Ci.nsINavHistoryResultNode.RESULT_TYPE_DYNAMIC_CONTAINER:
  466.           nodeData["dynamiccontainer"] = true;
  467.           break;
  468.         case Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER:
  469.         case Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT:
  470.           nodeData["folder"] = true;
  471.           break;
  472.         case Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR:
  473.           nodeData["separator"] = true;
  474.           break;
  475.         case Ci.nsINavHistoryResultNode.RESULT_TYPE_URI:
  476.         case Ci.nsINavHistoryResultNode.RESULT_TYPE_VISIT:
  477.         case Ci.nsINavHistoryResultNode.RESULT_TYPE_FULL_VISIT:
  478.           nodeData["link"] = true;
  479.           uri = PlacesUtils._uri(node.uri);
  480.           if (PlacesUtils.nodeIsBookmark(node)) {
  481.             nodeData["bookmark"] = true;
  482.             PlacesUtils.nodeIsTagQuery(node.parent)
  483.             var mss = PlacesUIUtils.microsummaries;
  484.             if (mss.hasMicrosummary(node.itemId))
  485.               nodeData["microsummary"] = true;
  486.  
  487.             var parentNode = node.parent;
  488.             if (parentNode) {
  489.               if (PlacesUtils.nodeIsTagQuery(parentNode))
  490.                 nodeData["tagChild"] = true;
  491.               else if (PlacesUtils.nodeIsLivemarkContainer(parentNode))
  492.                 nodeData["livemarkChild"] = true;
  493.             }
  494.           }
  495.           break;
  496.       }
  497.  
  498.       // annotations
  499.       if (uri) {
  500.         var names = PlacesUtils.annotations.getPageAnnotationNames(uri, {});
  501.         for (var j = 0; j < names.length; ++j)
  502.           nodeData[names[j]] = true;
  503.       }
  504.  
  505.       // For items also include the item-specific annotations
  506.       if (node.itemId != -1) {
  507.         names = PlacesUtils.annotations
  508.                            .getItemAnnotationNames(node.itemId, {});
  509.         for (j = 0; j < names.length; ++j)
  510.           nodeData[names[j]] = true;
  511.       }
  512.       metadata.push(nodeData);
  513.     }
  514.  
  515.     return metadata;
  516.   },
  517.  
  518.   /** 
  519.    * Determines if a context-menu item should be shown
  520.    * @param   aMenuItem
  521.    *          the context menu item 
  522.    * @param   aMetaData
  523.    *          meta data about the selection
  524.    * @returns true if the conditions (see buildContextMenu) are satisfied
  525.    *          and the item can be displayed, false otherwise. 
  526.    */
  527.   _shouldShowMenuItem: function PC__shouldShowMenuItem(aMenuItem, aMetaData) {
  528.     var selectiontype = aMenuItem.getAttribute("selectiontype");
  529.     if (selectiontype == "multiple" && aMetaData.length == 1)
  530.       return false;
  531.     if (selectiontype == "single" && aMetaData.length != 1)
  532.       return false;
  533.  
  534.     var forceHideRules = aMenuItem.getAttribute("forcehideselection").split("|");
  535.     for (var i = 0; i < aMetaData.length; ++i) {
  536.       for (var j=0; j < forceHideRules.length; ++j) {
  537.         if (forceHideRules[j] in aMetaData[i])
  538.           return false;
  539.       }
  540.     }
  541.  
  542.     var selectionAttr = aMenuItem.getAttribute("selection");
  543.     if (selectionAttr) {
  544.       if (selectionAttr == "any")
  545.         return true;
  546.  
  547.       var showRules = selectionAttr.split("|");
  548.       var anyMatched = false;
  549.       function metaDataNodeMatches(metaDataNode, rules) {
  550.         for (var i=0; i < rules.length; i++) {
  551.           if (rules[i] in metaDataNode)
  552.             return true;
  553.         }
  554.  
  555.         return false;
  556.       }
  557.       for (var i = 0; i < aMetaData.length; ++i) {
  558.         if (metaDataNodeMatches(aMetaData[i], showRules))
  559.           anyMatched = true;
  560.         else
  561.           return false;
  562.       }
  563.       return anyMatched;
  564.     }
  565.  
  566.     return !aMenuItem.hidden;
  567.   },
  568.  
  569.   /**
  570.    * Detects information (meta-data rules) about the current selection in the
  571.    * view (see _buildSelectionMetadata) and sets the visibility state for each
  572.    * of the menu-items in the given popup with the following rules applied:
  573.    *  1) The "selectiontype" attribute may be set on a menu-item to "single"
  574.    *     if the menu-item should be visible only if there is a single node
  575.    *     selected, or to "multiple" if the menu-item should be visible only if
  576.    *     multiple nodes are selected. If the attribute is not set or if it is
  577.    *     set to an invalid value, the menu-item may be visible for both types of
  578.    *     selection.
  579.    *  2) The "selection" attribute may be set on a menu-item to the various
  580.    *     meta-data rules for which it may be visible. The rules should be
  581.    *     separated with the | character.
  582.    *  3) A menu-item may be visible only if at least one of the rules set in
  583.    *     its selection attribute apply to each of the selected nodes in the
  584.    *     view.
  585.    *  4) The "forcehideselection" attribute may be set on a menu-item to rules
  586.    *     for which it should be hidden. This attribute takes priority over the
  587.    *     selection attribute. A menu-item would be hidden if at least one of the
  588.    *     given rules apply to one of the selected nodes. The rules should be
  589.    *     separated with the | character.
  590.    *  5) The "hideifnoinsetionpoint" attribute may be set on a menu-item to
  591.    *     true if it should be hidden when there's no insertion point
  592.    *  6) The visibility state of a menu-item is unchanged if none of these
  593.    *     attribute are set.
  594.    *  7) These attributes should not be set on separators for which the
  595.    *     visibility state is "auto-detected."
  596.    *  8) The "hideifprivatebrowsing" attribute may be set on a menu-item to
  597.    *     true if it should be hidden inside the private browsing mode
  598.    * @param   aPopup
  599.    *          The menupopup to build children into.
  600.    * @return true if at least one item is visible, false otherwise.
  601.    */
  602.   buildContextMenu: function PC_buildContextMenu(aPopup) {
  603.     var metadata = this._buildSelectionMetadata();
  604.     var ip = this._view.insertionPoint;
  605.     var noIp = !ip || ip.isTag;
  606.  
  607.     var separator = null;
  608.     var visibleItemsBeforeSep = false;
  609.     var anyVisible = false;
  610.     for (var i = 0; i < aPopup.childNodes.length; ++i) {
  611.       var item = aPopup.childNodes[i];
  612.       if (item.localName != "menuseparator") {
  613.         // We allow pasting into tag containers, so special case that.
  614.         var hideIfNoIP = item.getAttribute("hideifnoinsetionpoint") == "true" &&
  615.                          noIp && !(ip && ip.isTag && item.id == "placesContext_paste");
  616.         var hideIfPB = item.getAttribute("hideifprivatebrowsing") == "true" &&
  617.                        PlacesUIUtils.privateBrowsing.privateBrowsingEnabled;
  618.         item.hidden = hideIfNoIP || hideIfPB ||
  619.                       !this._shouldShowMenuItem(item, metadata);
  620.  
  621.         if (!item.hidden) {
  622.           visibleItemsBeforeSep = true;
  623.           anyVisible = true;
  624.  
  625.           // Show the separator above the menu-item if any
  626.           if (separator) {
  627.             separator.hidden = false;
  628.             separator = null;
  629.           }
  630.         }
  631.       }
  632.       else { // menuseparator
  633.         // Initially hide it. It will be unhidden if there will be at least one
  634.         // visible menu-item above and below it.
  635.         item.hidden = true;
  636.  
  637.         // We won't show the separator at all if no items are visible above it
  638.         if (visibleItemsBeforeSep)
  639.           separator = item;
  640.  
  641.         // New separator, count again:
  642.         visibleItemsBeforeSep = false;
  643.       }
  644.     }
  645.  
  646.     // Set Open Folder/Links In Tabs items enabled state if they're visible
  647.     if (anyVisible) {
  648.       var openContainerInTabsItem = document.getElementById("placesContext_openContainer:tabs");
  649.       if (!openContainerInTabsItem.hidden && this._view.selectedNode &&
  650.           PlacesUtils.nodeIsContainer(this._view.selectedNode)) {
  651.         openContainerInTabsItem.disabled =
  652.           !PlacesUtils.hasChildURIs(this._view.selectedNode);
  653.       }
  654.       else {
  655.         // see selectiontype rule in the overlay
  656.         var openLinksInTabsItem = document.getElementById("placesContext_openLinks:tabs");
  657.         openLinksInTabsItem.disabled = openLinksInTabsItem.hidden;
  658.       }
  659.     }
  660.  
  661.     return anyVisible;
  662.   },
  663.  
  664.   /**
  665.    * Select all links in the current view. 
  666.    */
  667.   selectAll: function PC_selectAll() {
  668.     this._view.selectAll();
  669.   },
  670.  
  671.   /**
  672.    * Opens the bookmark properties for the selected URI Node.
  673.    */
  674.   showBookmarkPropertiesForSelection: 
  675.   function PC_showBookmarkPropertiesForSelection() {
  676.     var node = this._view.selectedNode;
  677.     if (!node)
  678.       return;
  679.  
  680.     var itemType = PlacesUtils.nodeIsFolder(node) ||
  681.                    PlacesUtils.nodeIsTagQuery(node) ? "folder" : "bookmark";
  682.     var concreteId = PlacesUtils.getConcreteItemId(node);
  683.     var isRootItem = PlacesUtils.isRootItem(concreteId);
  684.     var itemId = node.itemId;
  685.     if (isRootItem || PlacesUtils.nodeIsTagQuery(node)) {
  686.       // If this is a root or the Tags query we use the concrete itemId to catch
  687.       // the correct title for the node.
  688.       itemId = concreteId;
  689.     }
  690.  
  691.     PlacesUIUtils.showItemProperties(itemId, itemType,
  692.                                      isRootItem /* read only */);
  693.   },
  694.  
  695.   /**
  696.    * This method can be run on a URI parameter to ensure that it didn't
  697.    * receive a string instead of an nsIURI object.
  698.    */
  699.   _assertURINotString: function PC__assertURINotString(value) {
  700.     NS_ASSERT((typeof(value) == "object") && !(value instanceof String), 
  701.            "This method should be passed a URI as a nsIURI object, not as a string.");
  702.   },
  703.  
  704.   /**
  705.    * Reloads the selected livemark if any.
  706.    */
  707.   reloadSelectedLivemark: function PC_reloadSelectedLivemark() {
  708.     var selectedNode = this._view.selectedNode;
  709.     if (selectedNode && PlacesUtils.nodeIsLivemarkContainer(selectedNode))
  710.       PlacesUtils.livemarks.reloadLivemarkFolder(selectedNode.itemId);
  711.   },
  712.  
  713.   /**
  714.    * Reload the microsummary associated with the selection
  715.    */
  716.   reloadSelectedMicrosummary: function PC_reloadSelectedMicrosummary() {
  717.     var selectedNode = this._view.selectedNode;
  718.     var mss = PlacesUIUtils.microsummaries;
  719.     if (mss.hasMicrosummary(selectedNode.itemId))
  720.       mss.refreshMicrosummary(selectedNode.itemId);
  721.   },
  722.  
  723.   /**
  724.    * Gives the user a chance to cancel loading lots of tabs at once
  725.    */
  726.   _confirmOpenTabs: function(numTabsToOpen) {
  727.     var pref = Cc["@mozilla.org/preferences-service;1"].
  728.                getService(Ci.nsIPrefBranch);
  729.  
  730.     const kWarnOnOpenPref = "browser.tabs.warnOnOpen";
  731.     var reallyOpen = true;
  732.     if (pref.getBoolPref(kWarnOnOpenPref)) {
  733.       if (numTabsToOpen >= pref.getIntPref("browser.tabs.maxOpenBeforeWarn")) {
  734.         var promptService = Cc["@mozilla.org/embedcomp/prompt-service;1"].
  735.                             getService(Ci.nsIPromptService);
  736.  
  737.         // default to true: if it were false, we wouldn't get this far
  738.         var warnOnOpen = { value: true };
  739.  
  740.         var messageKey = "tabs.openWarningMultipleBranded";
  741.         var openKey = "tabs.openButtonMultiple";
  742.         var strings = document.getElementById("placeBundle");
  743.         const BRANDING_BUNDLE_URI = "chrome://branding/locale/brand.properties";
  744.         var brandShortName = Cc["@mozilla.org/intl/stringbundle;1"].
  745.                              getService(Ci.nsIStringBundleService).
  746.                              createBundle(BRANDING_BUNDLE_URI).
  747.                              GetStringFromName("brandShortName");
  748.        
  749.         var buttonPressed = promptService.confirmEx(window,
  750.           PlacesUIUtils.getString("tabs.openWarningTitle"),
  751.           PlacesUIUtils.getFormattedString(messageKey, 
  752.             [numTabsToOpen, brandShortName]),
  753.           (promptService.BUTTON_TITLE_IS_STRING * promptService.BUTTON_POS_0)
  754.           + (promptService.BUTTON_TITLE_CANCEL * promptService.BUTTON_POS_1),
  755.           PlacesUIUtils.getString(openKey),
  756.           null, null,
  757.           PlacesUIUtils.getFormattedString("tabs.openWarningPromptMeBranded",
  758.             [brandShortName]),
  759.           warnOnOpen);
  760.  
  761.          reallyOpen = (buttonPressed == 0);
  762.          // don't set the pref unless they press OK and it's false
  763.          if (reallyOpen && !warnOnOpen.value)
  764.            pref.setBoolPref(kWarnOnOpenPref, false);
  765.       }
  766.     }
  767.     return reallyOpen;
  768.   },
  769.  
  770.   /**
  771.    * Opens the links in the selected folder, or the selected links in new tabs. 
  772.    */
  773.   openSelectionInTabs: function PC_openLinksInTabs(aEvent) {
  774.     var node = this._view.selectedNode;
  775.     if (node && PlacesUtils.nodeIsContainer(node))
  776.       PlacesUIUtils.openContainerNodeInTabs(this._view.selectedNode, aEvent);
  777.     else
  778.       PlacesUIUtils.openURINodesInTabs(this._view.getSelectionNodes(), aEvent);
  779.   },
  780.  
  781.   /**
  782.    * Shows the Add Bookmark UI for the current insertion point.
  783.    *
  784.    * @param aType
  785.    *        the type of the new item (bookmark/livemark/folder)
  786.    */
  787.   newItem: function PC_newItem(aType) {
  788.     var ip = this._view.insertionPoint;
  789.     if (!ip)
  790.       throw Cr.NS_ERROR_NOT_AVAILABLE;
  791.  
  792.     var performed = false;
  793.     if (aType == "bookmark")
  794.       performed = PlacesUIUtils.showAddBookmarkUI(null, null, null, ip);
  795.     else if (aType == "livemark")
  796.       performed = PlacesUIUtils.showAddLivemarkUI(null, null, null, null, ip);
  797.     else // folder
  798.       performed = PlacesUIUtils.showAddFolderUI(null, ip);
  799.  
  800.     if (performed) {
  801.       // select the new item
  802.       var insertedNodeId = PlacesUtils.bookmarks
  803.                                       .getIdForItemAt(ip.itemId, ip.index);
  804.       this._view.selectItems([insertedNodeId], false);
  805.     }
  806.   },
  807.  
  808.  
  809.   /**
  810.    * Create a new Bookmark folder somewhere. Prompts the user for the name
  811.    * of the folder. 
  812.    */
  813.   newFolder: function PC_newFolder() {
  814.     var ip = this._view.insertionPoint;
  815.     if (!ip)
  816.       throw Cr.NS_ERROR_NOT_AVAILABLE;
  817.  
  818.     var performed = false;
  819.     performed = PlacesUIUtils.showAddFolderUI(null, ip);
  820.     if (performed) {
  821.       // select the new item
  822.       var insertedNodeId = PlacesUtils.bookmarks
  823.                                       .getIdForItemAt(ip.itemId, ip.index);
  824.       this._view.selectItems([insertedNodeId], false);
  825.     }
  826.   },
  827.  
  828.   /**
  829.    * Create a new Bookmark separator somewhere.
  830.    */
  831.   newSeparator: function PC_newSeparator() {
  832.     var ip = this._view.insertionPoint;
  833.     if (!ip)
  834.       throw Cr.NS_ERROR_NOT_AVAILABLE;
  835.     var txn = PlacesUIUtils.ptm.createSeparator(ip.itemId, ip.index);
  836.     PlacesUIUtils.ptm.doTransaction(txn);
  837.     // select the new item
  838.     var insertedNodeId = PlacesUtils.bookmarks
  839.                                     .getIdForItemAt(ip.itemId, ip.index);
  840.     this._view.selectItems([insertedNodeId], false);
  841.   },
  842.  
  843.   /**
  844.    * Opens a dialog for moving the selected nodes.
  845.    */
  846.   moveSelectedBookmarks: function PC_moveBookmarks() {
  847.     window.openDialog("chrome://browser/content/places/moveBookmarks.xul",
  848.                       "", "chrome, modal",
  849.                       this._view.getSelectionNodes());
  850.   },
  851.  
  852.   /**
  853.    * Sort the selected folder by name
  854.    */
  855.   sortFolderByName: function PC_sortFolderByName() {
  856.     var itemId = PlacesUtils.getConcreteItemId(this._view.selectedNode);
  857.     var txn = PlacesUIUtils.ptm.sortFolderByName(itemId);
  858.     PlacesUIUtils.ptm.doTransaction(txn);
  859.   },
  860.  
  861.   /**
  862.    * Walk the list of folders we're removing in this delete operation, and
  863.    * see if the selected node specified is already implicitly being removed 
  864.    * because it is a child of that folder. 
  865.    * @param   node
  866.    *          Node to check for containment. 
  867.    * @param   pastFolders
  868.    *          List of folders the calling function has already traversed
  869.    * @returns true if the node should be skipped, false otherwise. 
  870.    */
  871.   _shouldSkipNode: function PC_shouldSkipNode(node, pastFolders) {
  872.     /**
  873.      * Determines if a node is contained by another node within a resultset. 
  874.      * @param   node
  875.      *          The node to check for containment for
  876.      * @param   parent
  877.      *          The parent container to check for containment in
  878.      * @returns true if node is a member of parent's children, false otherwise.
  879.      */
  880.     function isContainedBy(node, parent) {
  881.       var cursor = node.parent;
  882.       while (cursor) {
  883.         if (cursor == parent)
  884.           return true;
  885.         cursor = cursor.parent;
  886.       }
  887.       return false;
  888.     }
  889.   
  890.       for (var j = 0; j < pastFolders.length; ++j) {
  891.         if (isContainedBy(node, pastFolders[j]))
  892.           return true;
  893.       }
  894.       return false;
  895.   },
  896.  
  897.   /**
  898.    * Creates a set of transactions for the removal of a range of items. 
  899.    * A range is an array of adjacent nodes in a view.
  900.    * @param   [in] range
  901.    *          An array of nodes to remove. Should all be adjacent. 
  902.    * @param   [out] transactions
  903.    *          An array of transactions.
  904.    * @param   [optional] removedFolders
  905.    *          An array of folder nodes that have already been removed.
  906.    */
  907.   _removeRange: function PC__removeRange(range, transactions, removedFolders) {
  908.     NS_ASSERT(transactions instanceof Array, "Must pass a transactions array");
  909.     if (!removedFolders)
  910.       removedFolders = [];
  911.  
  912.     for (var i = 0; i < range.length; ++i) {
  913.       var node = range[i];
  914.       if (this._shouldSkipNode(node, removedFolders))
  915.         continue;
  916.  
  917.       if (PlacesUtils.nodeIsTagQuery(node.parent)) {
  918.         // This is a uri node inside a tag container.  It needs a special
  919.         // untag transaction.
  920.         var tagItemId = PlacesUtils.getConcreteItemId(node.parent);
  921.         var uri = PlacesUtils._uri(node.uri);
  922.         transactions.push(PlacesUIUtils.ptm.untagURI(uri, [tagItemId]));
  923.       }
  924.       else if (PlacesUtils.nodeIsTagQuery(node) && node.parent &&
  925.                PlacesUtils.nodeIsQuery(node.parent) &&
  926.                asQuery(node.parent).queryOptions.resultType ==
  927.                  Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_QUERY) {
  928.         // This is a tag container.
  929.         // Untag all URIs tagged with this tag only if the tag container is
  930.         // child of the "Tags" query in the library, in all other places we
  931.         // must only remove the query node.
  932.         var tag = node.title;
  933.         var URIs = PlacesUtils.tagging.getURIsForTag(tag);
  934.         for (var j = 0; j < URIs.length; j++)
  935.           transactions.push(PlacesUIUtils.ptm.untagURI(URIs[j], [tag]));
  936.       }
  937.       else if (PlacesUtils.nodeIsURI(node) &&
  938.                PlacesUtils.nodeIsQuery(node.parent) &&
  939.                asQuery(node.parent).queryOptions.queryType ==
  940.                  Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY) {
  941.         // This is a uri node inside an history query.
  942.         var bhist = PlacesUtils.history.QueryInterface(Ci.nsIBrowserHistory);
  943.         bhist.removePage(PlacesUtils._uri(node.uri));
  944.         // History deletes are not undoable, so we don't have a transaction.
  945.       }
  946.       else if (node.itemId == -1 &&
  947.                PlacesUtils.nodeIsQuery(node) &&
  948.                asQuery(node).queryOptions.queryType ==
  949.                  Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY) {
  950.         // This is a dynamically generated history query, like queries
  951.         // grouped by site, time or both.  Dynamically generated queries don't
  952.         // have an itemId even if they are descendants of a bookmark.
  953.         this._removeHistoryContainer(node);
  954.         // History deletes are not undoable, so we don't have a transaction.
  955.       }
  956.       else {
  957.         // This is a common bookmark item.
  958.         if (PlacesUtils.nodeIsFolder(node)) {
  959.           // If this is a folder we add it to our array of folders, used
  960.           // to skip nodes that are children of an already removed folder.
  961.           removedFolders.push(node);
  962.         }
  963.         transactions.push(PlacesUIUtils.ptm.removeItem(node.itemId));
  964.       }
  965.     }
  966.   },
  967.  
  968.   /**
  969.    * Removes the set of selected ranges from bookmarks.
  970.    * @param   txnName
  971.    *          See |remove|.
  972.    */
  973.   _removeRowsFromBookmarks: function PC__removeRowsFromBookmarks(txnName) {
  974.     var ranges = this._view.getRemovableSelectionRanges();
  975.     var transactions = [];
  976.     var removedFolders = [];
  977.  
  978.     for (var i = 0; i < ranges.length; i++)
  979.       this._removeRange(ranges[i], transactions, removedFolders);
  980.  
  981.     if (transactions.length > 0) {
  982.       var txn = PlacesUIUtils.ptm.aggregateTransactions(txnName, transactions);
  983.       PlacesUIUtils.ptm.doTransaction(txn);
  984.     }
  985.   },
  986.  
  987.   /**
  988.    * Removes the set of selected ranges from history.
  989.    */
  990.   _removeRowsFromHistory: function PC__removeRowsFromHistory() {
  991.     // Other containers are history queries, just delete from history
  992.     // history deletes are not undoable.
  993.     var nodes = this._view.getSelectionNodes();
  994.     var URIs = [];
  995.     var bhist = PlacesUtils.history.QueryInterface(Ci.nsIBrowserHistory);
  996.     var resultView = this._view.getResultView();
  997.     var root = this._view.getResultNode();
  998.  
  999.     for (var i = 0; i < nodes.length; ++i) {
  1000.       var node = nodes[i];
  1001.       if (PlacesUtils.nodeIsURI(node)) {
  1002.         var uri = PlacesUtils._uri(node.uri);
  1003.         // avoid trying to delete the same url twice
  1004.         if (URIs.indexOf(uri) < 0) {
  1005.           URIs.push(uri);
  1006.         }
  1007.       }
  1008.       else if (PlacesUtils.nodeIsQuery(node) &&
  1009.                asQuery(node).queryOptions.queryType ==
  1010.                  Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY)
  1011.         this._removeHistoryContainer(node);
  1012.     }
  1013.  
  1014.     // if we have to delete a lot of urls RemovePage will be slow, it's better
  1015.     // to delete them in bunch and rebuild the full treeView
  1016.     if (URIs.length > REMOVE_PAGES_MAX_SINGLEREMOVES) {
  1017.       // do removal in chunks to avoid passing a too big array to removePages
  1018.       for (var i = 0; i < URIs.length; i += REMOVE_PAGES_CHUNKLEN) {
  1019.         var URIslice = URIs.slice(i, i + REMOVE_PAGES_CHUNKLEN);
  1020.         // set DoBatchNotify (third param) only on the last chunk, so we update
  1021.         // the treeView when we are done.
  1022.         bhist.removePages(URIslice, URIslice.length,
  1023.                           (i + REMOVE_PAGES_CHUNKLEN) >= URIs.length);
  1024.       }
  1025.     }
  1026.     else {
  1027.       // if we have to delete fewer urls, removepage will allow us to avoid
  1028.       // rebuilding the full treeView
  1029.       for (var i = 0; i < URIs.length; ++i)
  1030.         bhist.removePage(URIs[i]);
  1031.     }
  1032.   },
  1033.  
  1034.   /**
  1035.    * Removes history visits for an history container node.
  1036.    * @param   [in] aContainerNode
  1037.    *          The container node to remove.
  1038.    */
  1039.   _removeHistoryContainer: function PC_removeHistoryContainer(aContainerNode) {
  1040.     var bhist = PlacesUtils.history.QueryInterface(Ci.nsIBrowserHistory);
  1041.     if (PlacesUtils.nodeIsHost(aContainerNode)) {
  1042.       // Site container.
  1043.       bhist.removePagesFromHost(aContainerNode.title, true);
  1044.     }
  1045.     else if (PlacesUtils.nodeIsDay(aContainerNode)) {
  1046.       // Day container.
  1047.       var query = aContainerNode.getQueries({})[0];
  1048.       var beginTime = query.beginTime;
  1049.       var endTime = query.endTime;
  1050.       NS_ASSERT(query && beginTime && endTime,
  1051.                 "A valid date container query should exist!");
  1052.       // We want to exclude beginTime from the removal because
  1053.       // removePagesByTimeframe includes both extremes, while date containers
  1054.       // exclude the lower extreme.  So, if we would not exclude it, we would
  1055.       // end up removing more history than requested.
  1056.       bhist.removePagesByTimeframe(beginTime+1, endTime);
  1057.     }
  1058.   },
  1059.  
  1060.   /**
  1061.    * Removes the selection
  1062.    * @param   aTxnName
  1063.    *          A name for the transaction if this is being performed
  1064.    *          as part of another operation.
  1065.    */
  1066.   remove: function PC_remove(aTxnName) {
  1067.     if (!this._hasRemovableSelection(false))
  1068.       return;
  1069.  
  1070.     NS_ASSERT(aTxnName !== undefined, "Must supply Transaction Name");
  1071.  
  1072.     var root = this._view.getResult().root;
  1073.  
  1074.     if (PlacesUtils.nodeIsFolder(root)) 
  1075.       this._removeRowsFromBookmarks(aTxnName);
  1076.     else if (PlacesUtils.nodeIsQuery(root)) {
  1077.       var queryType = asQuery(root).queryOptions.queryType;
  1078.       if (queryType == Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS)
  1079.         this._removeRowsFromBookmarks(aTxnName);
  1080.       else if (queryType == Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY)
  1081.         this._removeRowsFromHistory();
  1082.       else
  1083.         NS_ASSERT(false, "implement support for QUERY_TYPE_UNIFIED");
  1084.     }
  1085.     else
  1086.       NS_ASSERT(false, "unexpected root");
  1087.   },
  1088.  
  1089.   /**
  1090.    * Fills a DataTransfer object with the content of the selection that can be
  1091.    * dropped elsewhere.
  1092.    * @param   aEvent
  1093.    *          The dragstart event.
  1094.    */
  1095.   setDataTransfer: function PC_setDataTransfer(aEvent) {
  1096.     var dt = aEvent.dataTransfer;
  1097.     var doCopy = dt.effectAllowed == "copyLink" || dt.effectAllowed == "copy";
  1098.  
  1099.     var result = this._view.getResult();
  1100.     var oldViewer = result.viewer;
  1101.     try {
  1102.       result.viewer = null;
  1103.       var nodes = this._view.getDragableSelection();
  1104.  
  1105.       for (var i = 0; i < nodes.length; ++i) {
  1106.         var node = nodes[i];
  1107.  
  1108.         function addData(type, index, overrideURI) {
  1109.           var wrapNode = PlacesUtils.wrapNode(node, type, overrideURI, doCopy);
  1110.           dt.mozSetDataAt(type, wrapNode, index);
  1111.         }
  1112.  
  1113.         function addURIData(index, overrideURI) {
  1114.           addData(PlacesUtils.TYPE_X_MOZ_URL, index, overrideURI);
  1115.           addData(PlacesUtils.TYPE_UNICODE, index, overrideURI);
  1116.           addData(PlacesUtils.TYPE_HTML, index, overrideURI);
  1117.         }
  1118.  
  1119.         // This order is _important_! It controls how this and other 
  1120.         // applications select data to be inserted based on type.
  1121.         addData(PlacesUtils.TYPE_X_MOZ_PLACE, i);
  1122.  
  1123.         // Drop the feed uri for livemark containers
  1124.         if (PlacesUtils.nodeIsLivemarkContainer(node))
  1125.           addURIData(i, PlacesUtils.livemarks.getFeedURI(node.itemId).spec);
  1126.         else if (node.uri)
  1127.           addURIData(i);
  1128.       }
  1129.     }
  1130.     finally {
  1131.       if (oldViewer)
  1132.         result.viewer = oldViewer;
  1133.     }
  1134.   },
  1135.  
  1136.   /**
  1137.    * Copy Bookmarks and Folders to the clipboard
  1138.    */
  1139.   copy: function PC_copy() {
  1140.     var result = this._view.getResult();
  1141.     var oldViewer = result.viewer;
  1142.     try {
  1143.       result.viewer = null;
  1144.       var nodes = this._view.getSelectionNodes();
  1145.  
  1146.       var xferable =  Cc["@mozilla.org/widget/transferable;1"].
  1147.                       createInstance(Ci.nsITransferable);
  1148.       var foundFolder = false, foundLink = false;
  1149.       var copiedFolders = [];
  1150.       var placeString, mozURLString, htmlString, unicodeString;
  1151.       placeString = mozURLString = htmlString = unicodeString = "";
  1152.  
  1153.       for (var i = 0; i < nodes.length; ++i) {
  1154.         var node = nodes[i];
  1155.         if (this._shouldSkipNode(node, copiedFolders))
  1156.           continue;
  1157.         if (PlacesUtils.nodeIsFolder(node))
  1158.           copiedFolders.push(node);
  1159.         
  1160.         function generateChunk(type, overrideURI) {
  1161.           var suffix = i < (nodes.length - 1) ? NEWLINE : "";
  1162.           var uri = overrideURI;
  1163.         
  1164.           if (PlacesUtils.nodeIsLivemarkContainer(node))
  1165.             uri = PlacesUtils.livemarks.getFeedURI(node.itemId).spec
  1166.  
  1167.           mozURLString += (PlacesUtils.wrapNode(node, PlacesUtils.TYPE_X_MOZ_URL,
  1168.                                                  uri) + suffix);
  1169.           unicodeString += (PlacesUtils.wrapNode(node, PlacesUtils.TYPE_UNICODE,
  1170.                                                  uri) + suffix);
  1171.           htmlString += (PlacesUtils.wrapNode(node, PlacesUtils.TYPE_HTML,
  1172.                                                  uri) + suffix);
  1173.  
  1174.           var placeSuffix = i < (nodes.length - 1) ? "," : "";
  1175.           var resolveShortcuts = !PlacesControllerDragHelper.canMoveNode(node);
  1176.           return PlacesUtils.wrapNode(node, type, overrideURI, resolveShortcuts) + placeSuffix;
  1177.         }
  1178.  
  1179.         // all items wrapped as TYPE_X_MOZ_PLACE
  1180.         placeString += generateChunk(PlacesUtils.TYPE_X_MOZ_PLACE);
  1181.       }
  1182.  
  1183.       function addData(type, data) {
  1184.         xferable.addDataFlavor(type);
  1185.         xferable.setTransferData(type, PlacesUIUtils._wrapString(data), data.length * 2);
  1186.       }
  1187.       // This order is _important_! It controls how this and other applications 
  1188.       // select data to be inserted based on type.
  1189.       if (placeString)
  1190.         addData(PlacesUtils.TYPE_X_MOZ_PLACE, placeString);
  1191.       if (mozURLString)
  1192.         addData(PlacesUtils.TYPE_X_MOZ_URL, mozURLString);
  1193.       if (unicodeString)
  1194.         addData(PlacesUtils.TYPE_UNICODE, unicodeString);
  1195.       if (htmlString)
  1196.         addData(PlacesUtils.TYPE_HTML, htmlString);
  1197.  
  1198.       if (placeString || unicodeString || htmlString || mozURLString) {
  1199.         PlacesUIUtils.clipboard.setData(xferable, null, Ci.nsIClipboard.kGlobalClipboard);
  1200.       }
  1201.     }
  1202.     finally {
  1203.       if (oldViewer)
  1204.         result.viewer = oldViewer;
  1205.     }
  1206.   },
  1207.  
  1208.   /**
  1209.    * Cut Bookmarks and Folders to the clipboard
  1210.    */
  1211.   cut: function PC_cut() {
  1212.     this.copy();
  1213.     this.remove("Cut Selection");
  1214.   },
  1215.  
  1216.   /**
  1217.    * Paste Bookmarks and Folders from the clipboard
  1218.    */
  1219.   paste: function PC_paste() {
  1220.     // Strategy:
  1221.     // 
  1222.     // There can be data of various types (folder, separator, link) on the 
  1223.     // clipboard. We need to get all of that data and build edit transactions
  1224.     // for them. This means asking the clipboard once for each type and 
  1225.     // aggregating the results. 
  1226.  
  1227.     /**
  1228.      * Constructs a transferable that can receive data of specific types.
  1229.      * @param   types
  1230.      *          The types of data the transferable can hold, in order of
  1231.      *          preference.
  1232.      * @returns The transferable.
  1233.      */
  1234.     function makeXferable(types) {
  1235.       var xferable = 
  1236.           Cc["@mozilla.org/widget/transferable;1"].
  1237.           createInstance(Ci.nsITransferable);
  1238.       for (var i = 0; i < types.length; ++i) 
  1239.         xferable.addDataFlavor(types[i]);
  1240.       return xferable;
  1241.     }
  1242.  
  1243.     var clipboard = PlacesUIUtils.clipboard;
  1244.  
  1245.     var ip = this._view.insertionPoint;
  1246.     if (!ip)
  1247.       throw Cr.NS_ERROR_NOT_AVAILABLE;
  1248.  
  1249.     /**
  1250.      * Gets a list of transactions to perform the paste of specific types.
  1251.      * @param   types
  1252.      *          The types of data to form paste transactions for
  1253.      * @returns An array of transactions that perform the paste.
  1254.      */
  1255.     function getTransactions(types) {
  1256.       var xferable = makeXferable(types);
  1257.       clipboard.getData(xferable, Ci.nsIClipboard.kGlobalClipboard);
  1258.  
  1259.       var data = { }, type = { };
  1260.       try {
  1261.         xferable.getAnyTransferData(type, data, { });
  1262.         data = data.value.QueryInterface(Ci.nsISupportsString).data;
  1263.         var items = PlacesUtils.unwrapNodes(data, type.value);
  1264.         var transactions = [];
  1265.         var index = ip.index;
  1266.         for (var i = 0; i < items.length; ++i) {
  1267.           var txn;
  1268.           if (ip.isTag) {
  1269.             var uri = PlacesUtils._uri(items[i].uri);
  1270.             txn = PlacesUIUtils.ptm.tagURI(uri, [ip.itemId]);
  1271.           } 
  1272.           else {
  1273.             // adjusted to make sure that items are given the correct index
  1274.             // transactions insert differently if index == -1 
  1275.             // transaction will enqueue the item.
  1276.             if (ip.index > -1)
  1277.               index = ip.index + i;
  1278.             txn = PlacesUIUtils.makeTransaction(items[i], type.value,
  1279.                                                 ip.itemId, index, true);
  1280.           }
  1281.           transactions.push(txn);
  1282.         }
  1283.         return transactions;
  1284.       }
  1285.       catch (e) {
  1286.         // getAnyTransferData will throw if there is no data of the specified
  1287.         // type on the clipboard. 
  1288.         // unwrapNodes will throw if the data that is present is malformed in
  1289.         // some way. 
  1290.         // In either case, don't fail horribly, just return no data.
  1291.       }
  1292.       return [];
  1293.     }
  1294.  
  1295.     // Get transactions to paste any folders, separators or links that might
  1296.     // be on the clipboard, aggregate them and execute them. 
  1297.     var transactions = getTransactions([PlacesUtils.TYPE_X_MOZ_PLACE,
  1298.                                         PlacesUtils.TYPE_X_MOZ_URL, 
  1299.                                         PlacesUtils.TYPE_UNICODE]);
  1300.     var txn = PlacesUIUtils.ptm.aggregateTransactions("Paste", transactions);
  1301.     PlacesUIUtils.ptm.doTransaction(txn);
  1302.  
  1303.     // select the pasted items, they should be consecutive
  1304.     var insertedNodeIds = [];
  1305.     for (var i = 0; i < transactions.length; ++i)
  1306.       insertedNodeIds.push(PlacesUtils.bookmarks
  1307.                                       .getIdForItemAt(ip.itemId, ip.index + i));
  1308.     if (insertedNodeIds.length > 0)
  1309.       this._view.selectItems(insertedNodeIds, false);
  1310.   }
  1311. };
  1312.  
  1313. /**
  1314.  * Handles drag and drop operations for views. Note that this is view agnostic!
  1315.  * You should not use PlacesController._view within these methods, since
  1316.  * the view that the item(s) have been dropped on was not necessarily active. 
  1317.  * Drop functions are passed the view that is being dropped on. 
  1318.  */
  1319. var PlacesControllerDragHelper = {
  1320.   /**
  1321.    * DOM Element currently being dragged over
  1322.    */
  1323.   currentDropTarget: null,
  1324.  
  1325.   /**
  1326.    * Current nsIDOMDataTransfer
  1327.    * We need to cache this because we don't have access to the event in the
  1328.    * treeView's canDrop or drop methods, and session.dataTransfer would not be
  1329.    * filled for drag and drop from external sources (eg. the OS).
  1330.    */
  1331.   currentDataTransfer: null,
  1332.  
  1333.   /**
  1334.    * Determines if the mouse is currently being dragged over a child node of
  1335.    * this menu. This is necessary so that the menu doesn't close while the
  1336.    * mouse is dragging over one of its submenus
  1337.    * @param   node
  1338.    *          The container node
  1339.    * @returns true if the user is dragging over a node within the hierarchy of
  1340.    *          the container, false otherwise.
  1341.    */
  1342.   draggingOverChildNode: function PCDH_draggingOverChildNode(node) {
  1343.     var currentNode = this.currentDropTarget;
  1344.     while (currentNode) {
  1345.       if (currentNode == node)
  1346.         return true;
  1347.       currentNode = currentNode.parentNode;
  1348.     }
  1349.     return false;
  1350.   },
  1351.  
  1352.   /**
  1353.    * @returns The current active drag session. Returns null if there is none.
  1354.    */
  1355.   getSession: function PCDH__getSession() {
  1356.     var dragService = Cc["@mozilla.org/widget/dragservice;1"].
  1357.                       getService(Ci.nsIDragService);
  1358.     return dragService.getCurrentSession();
  1359.   },
  1360.  
  1361.   /**
  1362.    * Extract the first accepted flavor from a flavors array.
  1363.    * @param aFlavors
  1364.    *        The flavors array.
  1365.    */
  1366.   getFirstValidFlavor: function PCDH_getFirstValidFlavor(aFlavors) {
  1367.     for (var i = 0; i < aFlavors.length; i++) {
  1368.       if (this.GENERIC_VIEW_DROP_TYPES.indexOf(aFlavors[i]) != -1)
  1369.         return aFlavors[i];
  1370.     }
  1371.     return null;
  1372.   },
  1373.  
  1374.   /**
  1375.    * Determines whether or not the data currently being dragged can be dropped
  1376.    * on a places view.
  1377.    * @param ip
  1378.    *        The insertion point where the items should be dropped
  1379.    */
  1380.   canDrop: function PCDH_canDrop(ip) {
  1381.     var dt = this.currentDataTransfer;
  1382.     var dropCount = dt.mozItemCount;
  1383.  
  1384.     // Check every dragged item
  1385.     for (var i = 0; i < dropCount; i++) {
  1386.       var flavor = this.getFirstValidFlavor(dt.mozTypesAt(i));
  1387.       if (!flavor)
  1388.         return false;
  1389.  
  1390.       var data = dt.mozGetDataAt(flavor, i);
  1391.  
  1392.       // urls can be dropped on any insertionpoint
  1393.       // XXXmano: // Remember: this method is called for each dragover event!
  1394.       // Thus we shouldn't use unwrapNodes here at all if possible.
  1395.       // I think it would be OK to accept bogus data here (e.g. text which was
  1396.       // somehow wrapped as TAB_DROP_TYPE, this is not in our control, and
  1397.       // will just case the actual drop to be a no-op), and only rule out valid
  1398.       // expected cases, which are either unsupported flavors, or items which
  1399.       // cannot be dropped in the current insertionpoint. The last case will
  1400.       // likely force us to use unwrapNodes for the private data types of
  1401.       // places.
  1402.       if (flavor == TAB_DROP_TYPE)
  1403.         continue;
  1404.  
  1405.       try {
  1406.         var dragged = PlacesUtils.unwrapNodes(data, flavor)[0];
  1407.       } catch (e) {
  1408.         return false;
  1409.       }
  1410.  
  1411.       // Only bookmarks and urls can be dropped into tag containers
  1412.       if (ip.isTag && ip.orientation == Ci.nsITreeView.DROP_ON &&
  1413.           dragged.type != PlacesUtils.TYPE_X_MOZ_URL &&
  1414.           (dragged.type != PlacesUtils.TYPE_X_MOZ_PLACE ||
  1415.            /^place:/.test(dragged.uri)))
  1416.         return false;
  1417.  
  1418.       // The following loop disallows the dropping of a folder on itself or
  1419.       // on any of its descendants.
  1420.       if (dragged.type == PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER ||
  1421.           /^place:/.test(dragged.uri)) {
  1422.         var parentId = ip.itemId;
  1423.         while (parentId != PlacesUtils.placesRootId) {
  1424.           if (dragged.concreteId == parentId || dragged.id == parentId)
  1425.             return false;
  1426.           parentId = PlacesUtils.bookmarks.getFolderIdForItem(parentId);
  1427.         }
  1428.       }
  1429.     }
  1430.     return true;
  1431.   },
  1432.  
  1433.   
  1434.   /**
  1435.    * Determines if a node can be moved.
  1436.    * 
  1437.    * @param   aNode
  1438.    *          A nsINavHistoryResultNode node.
  1439.    * @returns True if the node can be moved, false otherwise.
  1440.    */
  1441.   canMoveNode:
  1442.   function PCDH_canMoveNode(aNode) {
  1443.     // can't move query root
  1444.     if (!aNode.parent)
  1445.       return false;
  1446.  
  1447.     var parentId = PlacesUtils.getConcreteItemId(aNode.parent);
  1448.     var concreteId = PlacesUtils.getConcreteItemId(aNode);
  1449.  
  1450.     // can't move children of tag containers
  1451.     if (PlacesUtils.nodeIsTagQuery(aNode.parent))
  1452.       return false;
  1453.  
  1454.     // can't move children of read-only containers
  1455.     if (PlacesUtils.nodeIsReadOnly(aNode.parent))
  1456.       return false;
  1457.  
  1458.     // check for special folders, etc
  1459.     if (PlacesUtils.nodeIsContainer(aNode) &&
  1460.         !this.canMoveContainer(aNode.itemId, parentId))
  1461.       return false;
  1462.  
  1463.     return true;
  1464.   },
  1465.  
  1466.   /**
  1467.    * Determines if a container node can be moved.
  1468.    * 
  1469.    * @param   aId
  1470.    *          A bookmark folder id.
  1471.    * @param   [optional] aParentId
  1472.    *          The parent id of the folder.
  1473.    * @returns True if the container can be moved to the target.
  1474.    */
  1475.   canMoveContainer:
  1476.   function PCDH_canMoveContainer(aId, aParentId) {
  1477.     if (aId == -1)
  1478.       return false;
  1479.  
  1480.     // Disallow moving of roots and special folders
  1481.     const ROOTS = [PlacesUtils.placesRootId, PlacesUtils.bookmarksMenuFolderId,
  1482.                    PlacesUtils.tagsFolderId, PlacesUtils.unfiledBookmarksFolderId,
  1483.                    PlacesUtils.toolbarFolderId];
  1484.     if (ROOTS.indexOf(aId) != -1)
  1485.       return false;
  1486.  
  1487.     // Get parent id if necessary
  1488.     if (aParentId == null || aParentId == -1)
  1489.       aParentId = PlacesUtils.bookmarks.getFolderIdForItem(aId);
  1490.  
  1491.     if (PlacesUtils.bookmarks.getFolderReadonly(aParentId))
  1492.       return false;
  1493.  
  1494.     return true;
  1495.   },
  1496.  
  1497.   /**
  1498.    * Handles the drop of one or more items onto a view.
  1499.    * @param   insertionPoint
  1500.    *          The insertion point where the items should be dropped
  1501.    */
  1502.   onDrop: function PCDH_onDrop(insertionPoint) {
  1503.     var dt = this.currentDataTransfer;
  1504.     var doCopy = dt.dropEffect == "copy";
  1505.  
  1506.     var transactions = [];
  1507.     var dropCount = dt.mozItemCount;
  1508.     var movedCount = 0;
  1509.     for (var i = 0; i < dropCount; ++i) {
  1510.       var flavor = this.getFirstValidFlavor(dt.mozTypesAt(i));
  1511.       if (!flavor)
  1512.         return false;
  1513.  
  1514.       var data = dt.mozGetDataAt(flavor, i);
  1515.       var unwrapped;
  1516.       if (flavor != TAB_DROP_TYPE) {
  1517.         // There's only ever one in the D&D case.
  1518.         unwrapped = PlacesUtils.unwrapNodes(data, flavor)[0];
  1519.       }
  1520.       else if (data instanceof XULElement && data.localName == "tab" &&
  1521.                data.ownerDocument.defaultView instanceof ChromeWindow) {
  1522.         var uri = data.linkedBrowser.currentURI;
  1523.         var spec = uri ? uri.spec : "about:blank";
  1524.         var title = data.label;
  1525.         unwrapped = { uri: spec,
  1526.                       title: data.label,
  1527.                       type: PlacesUtils.TYPE_X_MOZ_URL};
  1528.       }
  1529.       else
  1530.         throw("bogus data was passed as a tab")
  1531.  
  1532.       var index = insertionPoint.index;
  1533.  
  1534.       // Adjust insertion index to prevent reversal of dragged items. When you
  1535.       // drag multiple elts upward: need to increment index or each successive
  1536.       // elt will be inserted at the same index, each above the previous.
  1537.       var dragginUp = insertionPoint.itemId == unwrapped.parent &&
  1538.                       index < PlacesUtils.bookmarks.getItemIndex(unwrapped.id);
  1539.       if (index != -1 && dragginUp)
  1540.         index+= movedCount++;
  1541.  
  1542.       // if dragging over a tag container we should tag the item
  1543.       if (insertionPoint.isTag &&
  1544.           insertionPoint.orientation == Ci.nsITreeView.DROP_ON) {
  1545.         var uri = PlacesUtils._uri(unwrapped.uri);
  1546.         var tagItemId = insertionPoint.itemId;
  1547.         transactions.push(PlacesUIUtils.ptm.tagURI(uri,[tagItemId]));
  1548.       }
  1549.       else {
  1550.         transactions.push(PlacesUIUtils.makeTransaction(unwrapped,
  1551.                           flavor, insertionPoint.itemId,
  1552.                           index, doCopy));
  1553.       }
  1554.     }
  1555.  
  1556.     var txn = PlacesUIUtils.ptm.aggregateTransactions("DropItems", transactions);
  1557.     PlacesUIUtils.ptm.doTransaction(txn);
  1558.   },
  1559.  
  1560.   /**
  1561.    * Checks if we can insert into a container.
  1562.    * @param   aContainer
  1563.    *          The container were we are want to drop
  1564.    */
  1565.   disallowInsertion: function(aContainer) {
  1566.     NS_ASSERT(aContainer, "empty container");
  1567.     // allow dropping into Tag containers
  1568.     if (PlacesUtils.nodeIsTagQuery(aContainer))
  1569.       return false;
  1570.     // Disallow insertion of items under readonly folders
  1571.     return (!PlacesUtils.nodeIsFolder(aContainer) ||
  1572.              PlacesUtils.nodeIsReadOnly(aContainer));
  1573.   },
  1574.  
  1575.   placesFlavors: [PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER,
  1576.                   PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR,
  1577.                   PlacesUtils.TYPE_X_MOZ_PLACE],
  1578.  
  1579.   // The order matters.
  1580.   GENERIC_VIEW_DROP_TYPES: [PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER,
  1581.                             PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR,
  1582.                             PlacesUtils.TYPE_X_MOZ_PLACE,
  1583.                             PlacesUtils.TYPE_X_MOZ_URL,
  1584.                             TAB_DROP_TYPE,
  1585.                             PlacesUtils.TYPE_UNICODE],
  1586.  
  1587.   /**
  1588.    * Returns our flavourSet
  1589.    */
  1590.   get flavourSet() {
  1591.     delete this.flavourSet;
  1592.     var flavourSet = new FlavourSet();
  1593.     var acceptedDropFlavours = this.GENERIC_VIEW_DROP_TYPES;
  1594.     acceptedDropFlavours.forEach(flavourSet.appendFlavour, flavourSet);
  1595.     return this.flavourSet = flavourSet;
  1596.   }
  1597. };
  1598.  
  1599. function goUpdatePlacesCommands() {
  1600.   var placesController;
  1601.   try {
  1602.     // Or any other command...
  1603.     placesController = top.document.commandDispatcher
  1604.                           .getControllerForCommand("placesCmd_open");
  1605.   }
  1606.   catch(ex) { return; }
  1607.  
  1608.   function updatePlacesCommand(aCommand) {
  1609.     var enabled = false;
  1610.     if (placesController)
  1611.       enabled = placesController.isCommandEnabled(aCommand);
  1612.     goSetCommandEnabled(aCommand, enabled);
  1613.   }
  1614.  
  1615.   updatePlacesCommand("placesCmd_open");
  1616.   updatePlacesCommand("placesCmd_open:window");
  1617.   updatePlacesCommand("placesCmd_open:tab");
  1618.   updatePlacesCommand("placesCmd_new:folder");
  1619.   updatePlacesCommand("placesCmd_new:bookmark");
  1620.   updatePlacesCommand("placesCmd_new:livemark");
  1621.   updatePlacesCommand("placesCmd_new:separator");
  1622.   updatePlacesCommand("placesCmd_show:info");
  1623.   updatePlacesCommand("placesCmd_moveBookmarks");
  1624.   updatePlacesCommand("placesCmd_reload");
  1625.   updatePlacesCommand("placesCmd_reloadMicrosummary");
  1626.   updatePlacesCommand("placesCmd_sortBy:name");
  1627. }
  1628.